#!/usr/bin/env node
//@ts-check
const fs = require('fs')
const path = require('path')
const parser = new (require('./fxparser.min.js'))({ ignoreAttributes: false, preserveOrder: true })
const UCluster = new (require('./ufo_cluster.js'))()

/**
 * @param {object} xml 
 * @param {string} tag 
 */
function findTag(xml, tag) {
    let res = []
    for (const obj of xml) {
        const k = Object.keys(obj)[0]
        if (k === tag) {
            res.push(obj)
        }
    }
    return res.length === 0 ? undefined : res
}

/**
 * @param {object} xml 
 * @param {string} key 
 */
function findDictValue(xml, key) {
    for (const obj of xml) {
        if (!obj?.dict?.[0]) {
            continue
        }
        if (getText(obj.dict[0]?.key) === key) {
            return obj.dict[1]
        }
    }
    return undefined
}

/** @param {object} xml  */
function getText(xml) {
    return xml?.[0]?.['#text']
}

function scanGlyph(glyph, glyphsPath) {
    const glyphPath = path.join(glyphsPath, glyph + '.glif')
    if (!fs.existsSync(glyphPath)) {
        console.error('File not exist:', glyphPath, '(skip)')
        return []
    }

    const glyphXml = parser.parse(fs.readFileSync(glyphPath))
    // console.log('glyph', findTag(glyphXml, 'glyph')[0].glyph)
    const unicode = findTag(findTag(glyphXml, 'glyph')?.[0].glyph, 'unicode')
    if (unicode === undefined) {
        console.error('No unicode in glif:', glyphPath, '(skip)')
        return []
    }

    let result = []
    for (const u of unicode) {
        // console.log(glyph, u?.[':@']?.['@_hex'])
        const hex = parseInt(u?.[':@']?.['@_hex'], 16)
        if (isNaN(hex)) {
            console.error('Parsing glif error:', glyphPath, '(skip)')
            continue
        }
        // console.log(hex)
        result.push(String.fromCharCode(hex))
    }
    return result
}

function savetyGlyphName(glyph) {
    return glyph.replaceAll('.', '_').replaceAll('_vert', '.vert').replaceAll(/([A-Z])/g, '$1_')
}

/**
 * @typedef {{_REV: string[]} & {[char: string]: number}} FC
 * @param {string} ufo 
 * @return {Promise<FC | null>}
 */
async function generateGlyphs(ufo) {
    const libPath = path.join(ufo, 'lib.plist')
    if (!fs.existsSync(libPath)) {
        console.error('File not exist:', libPath)
        return null
    }
    const glyphsPath = path.join(ufo, 'glyphs')
    if (!fs.existsSync(glyphsPath)) {
        console.error('Directory not exist:', glyphsPath)
        return null
    }

    const libXml = parser.parse(fs.readFileSync(libPath, { encoding: "utf8" }))
    const glyphList = findDictValue(findTag(libXml, 'plist')?.[0].plist, 'public.glyphOrder')?.array
    if (!Array.isArray(glyphList)) {
        console.error('Parsing `lib.plist` error')
        return null
    }
    /** @ts-ignore */
    /** @type {FC}  */ let fc = { _REV: Array() }
    const jobs = glyphList.map(glyph => ({ action: 'scanGlyph', params: [savetyGlyphName(getText(glyph?.string)), glyphsPath] }))
    /** @ts-ignore */
    /** @type {string[][]} */ const results = await UCluster.project({ jobs })
    for (let i = 0; i < glyphList.length; i++) {
        const chars = results[i]
        for (const char of chars) {
            fc[char] = i
            fc._REV[i] = char
        }
    }

    // for (let i = 0; i < glyphList.length; i++) {
    //     const glyph = getText(glyphList[i]?.string).replaceAll('.', '_')
    //     const chars = scanGlyph(glyph, glyphsPath)
    //     for (const char of chars) {
    //         fc[char] = i
    //         fc._REV[i] = char
    //     }
    //     // if (i > 5) break
    // }
    return fc
}

function statDirGlyphs(dir) {
    const dirStat = fs.statSync(dir, { throwIfNoEntry: false })
    if (!dirStat?.isDirectory()) {
        return new Set()
    }
    const files = fs.readdirSync(dir, { withFileTypes: true }).filter(de => de.isFile() && de.name.endsWith('.xml'))
    let set = new Set()
    for (const { name } of files) {
        new Set(fs.readFileSync(path.join(dir, name), { encoding: "utf8" })).forEach(c => set.add(c))
    }
    return set
}

function statGlyphs(fc) {
    let codes = new Set()
    statDirGlyphs('zh_CN').forEach(c => codes.add(c))
    statDirGlyphs('zh_CN/Data').forEach(c => codes.add(c))
    statDirGlyphs('zh_CN/Data/Campaigns').forEach(c => codes.add(c))
    statDirGlyphs('zh_CN/Data/Scenarios').forEach(c => codes.add(c))
    // console.log(codes)
    const codeArr = Array.from(codes)
    const glyphsNull = codeArr.filter(c => !fc[c])
    let glyphs = codeArr.map(c => fc[c]).filter(c => c)
    glyphs.sort((a, b) => a - b)
    console.log(glyphs)

    // merge
    let result = []
    let lm = glyphs.length - 1
    for (let i = 0; i < glyphs.length; i++) {
        let v = glyphs[i]
        while (i < lm && glyphs[i + 1] == glyphs[i] + 1) {
            i++
        }
        if (v === glyphs[i]) {
            result.push(v.toString())
        }
        else {
            result.push(`${v}-${glyphs[i]}`)
        }
    }

    // min-max char
    let minChar = codeArr[0]
    let maxChar = codeArr[0]
    for (const c of codeArr) {
        if (c < minChar) {
            minChar = c
        }
        else if (c > maxChar) {
            maxChar = c
        }
    }

    return { result, glyphs, range: [minChar.charCodeAt(0), maxChar.charCodeAt(0)], codeArr, glyphsNull }
}

async function main(argv) {
    if (UCluster.isWorker) {
        UCluster.project({ worker: { 'scanGlyph': scanGlyph } })
        return
    }

    if (!argv[2]) {
        console.error('Usage: (node) ufo_parse.js <your_font.ufo>')
        process.exit(1)
    }
    if (!fs.existsSync(argv[2])) {
        console.error('Directory not exist:', argv[2])
        process.exit(1)
    }

    const fc = await generateGlyphs(argv[2])
    if (fc === null) {
        process.exit(1)
    }
    console.log(fc._REV.length, fc._REV.slice(0, 20))
    const gl = statGlyphs(fc)
    // console.log(fc)
    fs.writeFileSync('./log.txt', `Total ${gl.glyphs.length} glyphs:
  <String id="FONT_3_RANGE">${gl.result.join(',')}</String>
  <String id="CHARSET_RANGES">${gl.range[0]}-${gl.range[1]}</String>

Font Logs:
${JSON.stringify(fc)}

Glyph Logs:
${JSON.stringify(gl)}
`)
}

main(process.argv)
